/*
 * FPServerImpl.java
 *
 * Copyright 2008 David D. Emory
 * 
 * This file is part of Five Points. See <http://www.fpdev.org> for
 * additional information regarding the project.
 * 
 * Five Points is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Five Points is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Five Points.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.fpdev.apps.web;

import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import org.fpdev.core.FPEngine;
import org.fpdev.core.algo.path.Pathfinder;
import org.fpdev.core.basenet.*;
import org.fpdev.core.transit.Timetable;
import org.fpdev.util.FPUtil;

import java.rmi.*;
import java.rmi.server.*;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import org.fpdev.core.transit.CalendarService;
import org.fpdev.core.transit.Route;
import org.fpdev.core.transit.SubRoute;
import org.fpdev.core.transit.TimePoint;
import org.fpdev.core.trip.BikeTripItem;
import org.fpdev.core.trip.Trip;
import org.fpdev.util.CoordTransform;

/**
 * Implementation of the FPServer remote interface.
 * 
 * @author demory
 */
public class FPServerImpl extends UnicastRemoteObject implements FPServer, MonitorableServer {

  private FPEngine engine_;
  
  // request counts for various query types
  private int resolveQueries_,  nearbyLocsQueries_,  batchCoordQueries_, tripQueries_,  gmapQueries_,  rteGmapQueries_,  topoGraphQueries_;

  public FPServerImpl(FPEngine engine) throws RemoteException {
    engine_ = engine;
    resolveQueries_ = nearbyLocsQueries_ = batchCoordQueries_ = tripQueries_ = gmapQueries_ = rteGmapQueries_ = topoGraphQueries_ = 0;
  }

  public String get5PHome()  throws RemoteException {
    return engine_.getProperty("5PHome");
  }
  
  public synchronized String resolveLocationXML(String loc) throws RemoteException {
    resolveQueries_++;
    String xml = "";
    loc = loc.replaceAll("\\'", "");
    System.out.println(" ");
    System.out.println("--- RESOLVE QUERY #" + resolveQueries_ + " @ " + (new Date()) + "---");
    System.out.println("  Input:  " + loc);
    Iterator<Location> locs = engine_.getLocations().resolveLocationFromInput(loc).iterator();

    while (locs.hasNext()) {
      Location thisLoc = locs.next();
      xml += "<loc disp=\"" + thisLoc.getDispText(true) + "\" enc=\"" + thisLoc.getLocText() + "\"></loc>\n";
    }
    return xml;
  }

  public synchronized String findNearbyLocations(double x, double y, double r) throws RemoteException {
    nearbyLocsQueries_++;
    System.out.println(" ");
    System.out.println("--- NEARBYLOCS QUERY #" + nearbyLocsQueries_ + " @ " + (new Date()) + "---");
    System.out.println("  Location: (" + x + ", " + y + "), r=" + r);

    String xml = "";

    Point2D.Double pt = CoordTransform.fromLonLat(new Point2D.Double(x, y));
    pt.x *= 3.2808399;
    pt.y *= 3.2808399;

    Iterator<Location> locs = null;
    if (r == 0) {
      /*Iterator<Location> locs1000 = engine_.getLocations().locationsNearCoordinate(pt, 1000).iterator();
      Location closest = null;
      double bestDist = Double.MAX_VALUE;
      while (locs1000.hasNext()) {
        Location loc = locs1000.next();
        double thisDist = FPUtil.magnitude(pt.x, pt.y, loc.getNode().getX(), loc.getNode().getY());
        if (thisDist < bestDist) {
          bestDist = thisDist;
          closest = loc;
        }
      }*/
      Location closest = getClosestLocation(pt);
      if (closest != null) {
        locs = Collections.singleton(closest).iterator();
      }
    } else {
      locs = engine_.getLocations().locationsNearCoordinate(pt, r).iterator();
    }
    xml += "<?xml version=\"1.0\"?>\n";
    xml += "<locations>";
    while (locs != null && locs.hasNext()) {
      Location loc = locs.next();
      Point2D.Double ll = CoordTransform.toLonLat(CoordTransform.feetToMeters(loc.getNode().toPoint()));
      xml += "<loc disp=\"" + loc.getDispText(true) + "\" enc=\"" + loc.getLocText() + "\" x=\""+ll.getX()+"\" y=\""+ll.getY()+"\"></loc>\n";
    }
    xml += "</locations>";
    return xml;
  }

  public synchronized String batchCoordResolve(String coords) throws RemoteException {
    batchCoordQueries_++;
    System.out.println(" ");
    System.out.println("--- BATCH-COORD QUERY #" + batchCoordQueries_ + " @ " + (new Date()) + "---");
    System.out.println("  Coords: " + coords);

    String xml = "";
    xml += "<?xml version=\"1.0\"?>\n";
    xml += "<locations>";
    String[] coordArr = coords.split(";");
    for(String coord : coordArr) {
      String xyArr[] = coord.split(",");
      double x = new Double(xyArr[0]).doubleValue();
      double y = new Double(xyArr[1]).doubleValue();
      Point2D.Double pt = CoordTransform.fromLonLat(new Point2D.Double(x, y));
      pt.x *= 3.2808399;
      pt.y *= 3.2808399;

      Location closest = getClosestLocation(pt);

      xml += "<loc enc=\"" + (closest == null ? "" : closest.getLocText()) + "\" />";
    }
    xml += "</locations>";
    return xml;  }

  public Location getClosestLocation(Point2D.Double pt) {
    Iterator<Location> locs = engine_.getLocations().locationsNearCoordinate(pt, 1000).iterator();
    Location closest = null;
    double bestDist = Double.MAX_VALUE;
    while (locs.hasNext()) {
      Location loc = locs.next();
      double thisDist = FPUtil.magnitude(pt.x, pt.y, loc.getNode().getX(), loc.getNode().getY());
      if (thisDist < bestDist) {
        bestDist = thisDist;
        closest = loc;
      }
    }
    return closest;
  }

  public synchronized String planTrip(short scenID, int triptype, String start, String end, int hour, int min, int ampm, String serviceStr, String deparrStr, String optmodeStr, double nmspeed, double nmradius, double typefact, double topfact) throws RemoteException {
    tripQueries_++;
    try {
      System.out.println("");
      System.out.println("--- TRIP QUERY #" + tripQueries_ + " @ " + (new Date()) + "---");
      long qStartTime = new Date().getTime();
      //Timetable.DayOfWeek day = null;
      CalendarService service = null;
      Pathfinder.TimeMode deparr = null;
      int optmode = 0;


      /*if (dayStr.equals("mf")) {
        day = Timetable.DayOfWeek.WEEKDAY;
      } else if (dayStr.equals("sat")) {
        day = Timetable.DayOfWeek.SATURDAY;
      } else if (dayStr.equals("sun")) {
        day = Timetable.DayOfWeek.SUNDAY;
      }*/
      if(serviceStr.startsWith("date:")) {
        // todo: implement service resolution
      }
      else
        service = engine_.getRoutes().getCalendarServices().getService(serviceStr);
      
      if (deparrStr.equals("dep")) {
        deparr = Pathfinder.TimeMode.DEPART_AT;
      } else if (deparrStr.equals("arr")) {
        deparr = Pathfinder.TimeMode.ARRIVE_BY;
      }

      if (optmodeStr.equals("time")) {
        optmode = Pathfinder.TRAVEL_TIME;
      } else if (optmodeStr.equals("transfer")) {
        optmode = Pathfinder.NUM_TRANSFERS;
      } else if (optmodeStr.equals("walk")) {
        optmode = Pathfinder.WALK_DISTANCE;
      }

      System.out.println("  Start Location:  " + start);
      System.out.println("  End Location:  " + end);
      System.out.println("  Trip Day/Time:  " + service.getID() + "_" + hour + ":" + min + "_" + ampm);
      System.out.println("  NM Speed & Radius: " + nmspeed + " " + nmradius);
      System.out.println("  Bike Type/Top Factors: " + typefact + " " + topfact);

      String errHeader = "<div style=\"font-family:sans-serif; text-align:center; margin-top:12px; \">";
      String errFooter = "</div>";

      Location startLoc = engine_.getLocations().getResolvedLocation(start);
      BNode startNode = startLoc.getNode(); //new CNodeAddress(startLink, CUtil.getAddressNumber(start));
      if (startNode == null) {
        return errHeader + "Could not resolve start location" + errFooter;
      }
      //String processedStart = engine_.getProcessedLocationStr();	

      //CLinkStreet endLink = engine_.getLinkFromAddress(end);
      //if(endLink == null) return "Could not locate end address";  	
      Location endLoc = engine_.getLocations().getResolvedLocation(end);
      BNode endNode = endLoc.getNode(); //new CNodeAddress(endLink, CUtil.getAddressNumber(end));
      if (endNode == null) {
        return errHeader + "Could not resolve end location" + errFooter;
      }
      //String processedEnd = engine_.getProcessedLocationStr();	

      int time = FPUtil.getSTime(hour, min, (ampm == 0 ? FPUtil.AM : FPUtil.PM)); //CUtil.getSTime(5,0,CUtil.PM);
      double walkSpeed = nmspeed * (5280.0 / 3600.0);
      double walkLimit = nmradius * 5280.0;

      // perform trip query 
      System.out.println("triptype=" + triptype);
      boolean success = engine_.getPathfinder().findShortestTrip(engine_.getScenarios().get(scenID), triptype, time, service, deparr, walkSpeed, (int) walkLimit, typefact, topfact, startNode, endNode);
      long qEndTime = new java.util.Date().getTime();
      String qLenSec = "" + (double) Math.round((double) qEndTime - qStartTime) / 1000.0;
      String xml = "";
      xml += "<?xml version=\"1.0\"?>\n";
      xml += "<plantrip server=\"edgewood.cfpt.org\" qtime=\"" + FPUtil.currentTimeStr() + "\" qlensec=\"" + qLenSec + "\" >\n";
      if (success) {
        if (triptype == Pathfinder.BIKE_TO_TRANSIT || triptype == Pathfinder.WALK_TO_TRANSIT) {
          // find best travel-time trip
          Trip bestTT = engine_.getPathfinder().getBestTripTT();
          bestTT.addLinkListToDB(engine_.getDataPackage().getCoreDB());
          xml += bestTT.getXml(engine_.getBaseNet());

          // find best number-of-transfers trip
          Trip bestNT = engine_.getPathfinder().getBestTripNT();
          bestNT.addLinkListToDB(engine_.getDataPackage().getCoreDB());
          xml += bestNT.getXml(engine_.getBaseNet());

        } else {
          // single best trip if non-motorized
          Trip bestNM = engine_.getPathfinder().getBestTripNM();
          bestNM.addLinkListToDB(engine_.getDataPackage().getCoreDB());
          xml += bestNM.getXml(engine_.getBaseNet());
        }
      } else { // no trip found
        Iterator<String> msgs = engine_.getPathfinder().getMessages();
        while (msgs.hasNext()) {
          xml += "  <msg>" + msgs.next() + "</msg>\n";
        }
      }
      xml += "</plantrip>\n";

      //engine_.getDB().logTripQuery(101, 0, 1, triptype, start, end, time, day.numCode(), deparr, walkSpeed, walkLimit, typefact, topfact, success ? 1 : 0);

      return xml;

    } catch (Exception e) {
      StackTraceElement[] st = e.getStackTrace();
      String r = e.toString() + "<br/>";
      for (int i = 0; i < st.length; i++) {
        r += st[i].toString() + "<br/>";
      }
      return r;
    }
  }

  public synchronized String getGMapOverlay(String linkStr) throws RemoteException {
    gmapQueries_++;
    System.out.println(" ");
    System.out.println("--- GMAPOVERLAY QUERY #" + gmapQueries_ + " @ " + (new Date()) + "---");
    System.out.println("  LinkStr: " + (linkStr.length() > 60 ? linkStr.substring(0, 59) + "..." : linkStr));

    Path curPath = null;
    String curMode = null;
    List<Path> paths = new LinkedList<Path>();
    List<String> modes = new LinkedList<String>();

    BLink link;
    String[] linkArr = linkStr.split(",");
    for (int l = 0; l < linkArr.length; l++) {
      String lStr = linkArr[l];
      if (lStr.matches("[0-9]+")) {
        //System.out.println("path link "+lStr);
        link = engine_.getLinkFromID(new Integer(lStr).intValue());
        //if(link != null) link.drawG2D(g, cc);
        curPath.addToEnd(link);
      } else {
        if (curPath != null) {
          paths.add(curPath);
          modes.add(curMode);
        }
        curPath = new Path(engine_);
        curMode = linkArr[l];
      }
    }
    paths.add(curPath);
    modes.add(curMode);

    String xml = "";
    xml += "<?xml version=\"1.0\"?>\n";
    xml += "<overlay>\n";
    for (int p = 0; p < paths.size(); p++) {
      String mode = modes.get(p);
      Path path = paths.get(p);
      xml += "<segment mode=\"" + mode + "\">\n";
      Iterator<BNode> nodes = path.getNodes();
      Iterator<BLink> links = path.getLinks();
      while (nodes.hasNext()) {
        BNode node = nodes.next();
        Point2D.Double ll = CoordTransform.toLonLat(CoordTransform.feetToMeters(node.toPoint())); //new Point2D.Double(node.getX() / 3.2808399, node.getY() / 3.2808399));
        xml += "<point x=\"" + ll.x + "\" y=\"" + ll.y + "\"/>";

        if (links.hasNext()) { // shapepoints
          link = links.next();
          ListIterator<Point2D.Double> shpPts = link.getShapePointsList().listIterator();
          boolean reverse = (node == link.getTNode());
          while (reverse ? shpPts.hasPrevious() : shpPts.hasNext()) {
            Point2D.Double pt = (reverse ? shpPts.previous() : shpPts.next());
            ll = CoordTransform.toLonLat(new Point2D.Double(pt.x / 3.2808399, pt.y / 3.2808399));
            xml += "<point x=\"" + ll.x + "\" y=\"" + ll.y + "\"/>";
          }
        }
      }
      xml += "</segment>\n";
    }

    xml += "</overlay>";
    return xml;
  }

  public synchronized String getGMapOverlayFromID(int id) throws RemoteException {
    gmapQueries_++;
    System.out.println(" ");
    System.out.println("--- GMAPOVERLAY(ID) QUERY #" + gmapQueries_ + " @ " + (new Date()) + "---");
    System.out.println("  id="+id);

    String linkStr = engine_.getDataPackage().getCoreDB().getLinkList(id);

    System.out.println("  LinkStr: " + (linkStr.length() > 60 ? linkStr.substring(0, 59) + "..." : linkStr));
    System.out.println("");


    Path curPath = null;
    String curMode = null;
    List<Path> paths = new LinkedList<Path>();
    List<String> modes = new LinkedList<String>();

    BLink link;
    String[] linkArr = linkStr.split(",");
    for (int l = 1; l < linkArr.length; l++) {
      String lStr = linkArr[l];
      if (lStr.matches("[0-9]+")) {
        //System.out.println("path link "+lStr);
        link = engine_.getLinkFromID(new Integer(lStr).intValue());
        //if(link != null) link.drawG2D(g, cc);
        curPath.addToEnd(link);
      } else {
        if (curPath != null) {
          paths.add(curPath);
          modes.add(curMode);
        }
        curPath = new Path(engine_);
        curMode = linkArr[l];
      }
    }
    paths.add(curPath);
    modes.add(curMode);

    String xml = "";
    xml += "<?xml version=\"1.0\"?>\n";
    xml += "<overlay>\n";
    for (int p = 0; p < paths.size(); p++) {
      String mode = modes.get(p);
      Path path = paths.get(p);
      xml += "<segment mode=\"" + mode + "\">\n";
      Iterator<BNode> nodes = path.getNodes();
      Iterator<BLink> links = path.getLinks();
      while (nodes.hasNext()) {
        BNode node = nodes.next();
        Point2D.Double ll = CoordTransform.toLonLat(new Point2D.Double(node.getX() / 3.2808399, node.getY() / 3.2808399));
        xml += "<point x=\"" + ll.x + "\" y=\"" + ll.y + "\"/>";

        if (links.hasNext()) { // shapepoints
          link = links.next();
          if(link.shapePointsCount() == 0) continue;
          boolean reverse = (node == link.getTNode());
          ListIterator<Point2D.Double> shpPts = link.getShapePointsList().listIterator(reverse ? link.shapePointsCount()-1: 0);
          while (reverse ? shpPts.hasPrevious() : shpPts.hasNext()) {
            Point2D.Double pt = (reverse ? shpPts.previous() : shpPts.next());
            ll = CoordTransform.toLonLat(new Point2D.Double(pt.x / 3.2808399, pt.y / 3.2808399));
            xml += "<point x=\"" + ll.x + "\" y=\"" + ll.y + "\"/>";
          }
        }
      }
      xml += "</segment>\n";
    }

    xml += "</overlay>";
    return xml;
  }

  public synchronized String getRouteGMapOverlay(String fullID) throws RemoteException {

    rteGmapQueries_++;
    System.out.println(" ");
    System.out.println("--- RTEGMAPOVERLAY QUERY #" + rteGmapQueries_ + " @ " + (new Date()) + "---");
    System.out.println("  FullID: " + fullID);

    String xml = "";
    xml += "<?xml version=\"1.0\"?>\n";
    xml += "<rteoverlay>\n";

    String rteID = fullID.split(":")[0];
    String subID = fullID.split(":")[1];
    Route rte = engine_.getRoutes().getRoute(rteID);
    if (rte != null) {
      SubRoute sub = rte.getSubRoute(subID);
      if (sub != null) {
        Path path = sub.getPath();

        xml += "<segment>\n";
        Iterator<BNode> nodes = path.getNodes();
        Iterator<BLink> links = path.getLinks();
        while (nodes.hasNext()) {
          BNode node = nodes.next();
          Point2D.Double ll = CoordTransform.toLonLat(new Point2D.Double(node.getX() / 3.2808399, node.getY() / 3.2808399));
          xml += "<point x=\"" + ll.x + "\" y=\"" + ll.y + "\"/>\n";

          if (links.hasNext()) { // shapepoints
            BLink link = links.next();
            ListIterator<Point2D.Double> shpPts = link.getShapePointsList().listIterator();
            boolean reverse = (node == link.getTNode());
            while (reverse ? shpPts.hasPrevious() : shpPts.hasNext()) {
              Point2D.Double pt = (reverse ? shpPts.previous() : shpPts.next());
              ll = CoordTransform.toLonLat(new Point2D.Double(pt.x / 3.2808399, pt.y / 3.2808399));
              xml += "<point x=\"" + ll.x + "\" y=\"" + ll.y + "\"/>\n";
            }
          }
        }
        xml += "</segment>\n";

        xml += "<timepts>\n";
        Iterator<TimePoint> tpts = sub.getPath().getTimePoints();
        while (tpts.hasNext()) {
          TimePoint tpt = tpts.next();
          Point2D.Double ll = CoordTransform.toLonLat(new Point2D.Double(tpt.getNode().getX() / 3.2808399, tpt.getNode().getY() / 3.2808399));
          xml += "<timept x=\"" + ll.x + "\" y=\"" + ll.y + "\">" + tpt.getNode().getDisplayName(engine_.getBaseNet()) + "</timept>\n";
        }
        xml += "</timepts>\n";
      }
    }

    xml += "</rteoverlay>";
    return xml;
  }

  public synchronized int[] getTopoGraph(String linkStr) throws RemoteException {

    topoGraphQueries_++;
    System.out.println(" ");
    System.out.println("--- TOPOGRAPH QUERY #" + topoGraphQueries_ + " @ " + (new Date()) + "---");
    System.out.println("  TopoLinks: " + (linkStr.length() > 60 ? linkStr.substring(0, 59) + "..." : linkStr));

    BikeTripItem bikeTrip = (BikeTripItem) engine_.getPathfinder().bikeLinksToTrip(linkStr);
    BufferedImage img = bikeTrip.getElevGraph(engine_);
    int width = img.getWidth();
    int height = img.getHeight();
    return img.getRGB(0, 0, width, height, null, 0, width);
  }

  public synchronized int[] getTopoGraphFromID(int id, int height) throws RemoteException {

    topoGraphQueries_++;
    System.out.println(" ");
    System.out.println("--- TOPOGRAPH(ID) QUERY #" + topoGraphQueries_ + " @ " + (new Date()) + "---");
    System.out.println("  id="+id);
    
    String linkStr = engine_.getDataPackage().getCoreDB().getLinkList(id);
    
    System.out.println("  TopoLinks: " + (linkStr.length() > 60 ? linkStr.substring(0, 59) + "..." : linkStr));

    BikeTripItem bikeTrip = (BikeTripItem) engine_.getPathfinder().bikeLinksToTrip(linkStr);
    System.out.println("  converted to bike trip");
    BufferedImage img = bikeTrip.getElevGraph(engine_, height);
    System.out.println("  generated image");
    int width = img.getWidth();
    //int height = img.getHeight();
    return img.getRGB(0, 0, width, height, null, 0, width);
  }

  public void monitor() {
    System.out.println("Resolve queries processed: " + resolveQueries_);
    System.out.println("Trip queries processed: " + tripQueries_);
  }

}






















